package com.jml.滑动窗口计数器;

import java.util.LinkedList;

/**
 * 滑动窗口计数器算法 是固定窗口计数器的升级版本，固定计数器窗口算法缺陷，
 * 如果流量突然激增,比如我们限制一个接口一分钟只能访问1000次的话,用户恶意在第一分钟的50秒堆积发送1000次请求,
 * 第二分钟10s堆积发送1000次请求，这样的话相当于在1分钟内收到2000次请求，失去限流保护的意义。
 */
public class JmlSlideWindow {
    /**
     * 定义一个 链表，链表存的是时间
     */
    private LinkedList<Long> linkedList;

    /*
     限流的次数  例如：2次
     */
    private Integer count;
    /**
     * 窗口时间 例如 10s
     */
    private Long timeWindow;

    public JmlSlideWindow(Integer count, Long timeWindow) {
        this.count = count;
        this.timeWindow = timeWindow;
        linkedList = new LinkedList<>();
    }


    public boolean tryAcquire() {
        // 获取到当前系统时间
        long currentTimeMillis = System.currentTimeMillis();
        //链表还没满，表示还能放入
        if (count - linkedList.size() > 0) {
            // 最新的 node 节点存放 在最前面
            linkedList.add(0, currentTimeMillis);
            return true;// 没有被限流
        }
        // 判断是否在10s(固定范围)范围内
        int farIndex = count - 1;
        // 取出尾结点
        Long farTime = linkedList.get(farIndex);
        //还没到时间就已经满了
        if (currentTimeMillis - farTime < timeWindow) {
            return false;
        }
        //已经超过限定时间范围内,把最后一个时间节点删除，新加入一个时间节点
        linkedList.remove(farIndex);// 删除尾结点
        linkedList.add(0, currentTimeMillis);
        return true;
    }

    public static void main(String[] args) throws InterruptedException {
        LinkedList a = new LinkedList<>();
        a.add(0, "1");
        a.add(0, "2");
        a.add(0, "3");
        a.add(0, "4");
        a.add(0, "5");
        a.remove(3);
        System.out.println(a);

        JmlSlideWindow jmlSlideWindow = new JmlSlideWindow(2, 10000L);
        for (int i = 1; i <= 100; i++) {
            Thread.sleep(1000);
            // 10s 内只能够访问2次。
            boolean result = jmlSlideWindow.tryAcquire();
            System.out.println("i" + i + "," + result);
        }
    }
}
